/*
 * Copyright (C) 2021 The Chinese Software International Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.tonyodev.fetch2.helper;

import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.app.Context;
import ohos.event.commonevent.CommonEventData;
import ohos.event.commonevent.CommonEventManager;
import ohos.event.commonevent.CommonEventPublishInfo;
import ohos.event.commonevent.CommonEventSubscribeInfo;
import ohos.event.commonevent.CommonEventSubscriber;
import ohos.event.commonevent.MatchingSkills;
import ohos.rpc.RemoteException;

import com.tonyodev.fetch2.Download;
import com.tonyodev.fetch2.FetchIntent;
import com.tonyodev.fetch2.NetworkType;
import com.tonyodev.fetch2.PrioritySort;
import com.tonyodev.fetch2.downloader.DownloadManager;
import com.tonyodev.fetch2.fetch.ListenerCoordinator;
import com.tonyodev.fetch2.provider.DownloadProvider;
import com.tonyodev.fetch2.provider.NetworkInfoProvider;
import com.tonyodev.fetch2.util.Defaults;
import com.tonyodev.fetch2core.FetchCoreUtils;
import com.tonyodev.fetch2core.HandlerWrapper;
import com.tonyodev.fetch2core.Logger;

import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.tonyodev.fetch2.FetchIntent.ACTION_QUEUE_BACKOFF_RESET;

/**
 * description PriorityListProcessorImpl
 *
 * @author liuluming
 * @since 2021-05-25
 */
public class PriorityListProcessorImpl implements PriorityListProcessor {
    private Object lock;
    @NotNull
    private volatile NetworkType globalNetworkType;
    private volatile boolean paused;
    private volatile boolean stopped;
    private volatile long backOffTime = 1800;
    private NetworkInfoProvider.NetworkChangeListener networkChangeListener;
    private PriorityBackoffResetReceiver priorityBackoffResetReceiver;
    private HandlerWrapper handlerWrapper;
    private DownloadProvider downloadProvider;
    private DownloadManager downloadManager;
    private NetworkInfoProvider networkInfoProvider;
    private Logger logger;
    private ListenerCoordinator listenerCoordinator;
    private volatile int downloadConcurrentLimit;
    private Context context;
    private String namespace;
    private PrioritySort prioritySort;

    @NotNull
    public NetworkType getGlobalNetworkType() {
        return this.globalNetworkType;
    }

    public void setGlobalNetworkType(@NotNull NetworkType mNetworkType) {
        this.globalNetworkType = mNetworkType;
    }

    public boolean isPaused() {
        return this.paused;
    }

    public boolean isStopped() {
        return this.stopped;
    }

    public void start() {
        synchronized (lock) {
            this.resetBackOffTime();
            this.stopped = false;
            this.paused = false;
            this.logger.d("PriorityIterator started");
        }
    }

    public void stop() {
        synchronized (lock) {
            this.unregisterPriorityIterator();
            this.paused = false;
            this.stopped = true;
            this.downloadManager.cancelAll();
            this.logger.d("PriorityIterator stop");
        }
    }

    public void pause() {
        synchronized (lock) {
            this.unregisterPriorityIterator();
            this.paused = true;
            this.stopped = false;
            this.downloadManager.cancelAll();
            this.logger.d("PriorityIterator paused");

        }
    }

    public void resume() {
        synchronized (lock) {
            this.resetBackOffTime();
            this.paused = false;
            this.stopped = false;
            this.registerPriorityIterator();
            this.logger.d("PriorityIterator resumed");
        }
    }

    /**
     * getPriorityList
     *
     * @return true
     */
    @NotNull
    public List<Download> getPriorityList() {
        synchronized (lock) {
            return (List<Download>) downloadProvider.getPendingDownloadsSorted(prioritySort);
        }
    }

    private void registerPriorityIterator() {
        if (this.getDownloadConcurrentLimit() > 0) {
            this.handlerWrapper.postDelayed(this.priorityIteratorRunnable, this.backOffTime);
        }
    }

    private void unregisterPriorityIterator() {
        if (this.getDownloadConcurrentLimit() > 0) {
            this.handlerWrapper.removeCallbacks(this.priorityIteratorRunnable);
        }
    }

    private boolean canContinueToProcess() {
        return !this.stopped && !this.paused;
    }

    public void resetBackOffTime() {
        synchronized (lock) {
            backOffTime = Defaults.DEFAULT_PRIORITY_QUEUE_INTERVAL_IN_MILLISECONDS;
            this.unregisterPriorityIterator();
            this.registerPriorityIterator();
            this.logger.d("PriorityIterator backoffTime reset to " + this.backOffTime + " milliseconds");
        }
    }

    public void sendBackOffResetSignal() {
        synchronized (lock) {
            Intent intent = new Intent();
            Operation operation = new Intent.OperationBuilder()
                .withAction(ACTION_QUEUE_BACKOFF_RESET)
                .build();
            intent.setOperation(operation);
            intent.setParam(FetchIntent.EXTRA_NAMESPACE, namespace);
            CommonEventData eventData = new CommonEventData(intent);

            CommonEventPublishInfo publishInfo = new CommonEventPublishInfo();
            String[] permissions = {"com.tonyodev.fetch2.permission.PUBLISHINFO"};
            publishInfo.setSubscriberPermissions(permissions); // 设置权限

            try {
                CommonEventManager.publishCommonEvent(eventData, publishInfo);  // 发布公共事件。
            } catch (RemoteException e) {
                logger.d("Exception occurred during publishCommonEvent invocation.");
            }
        }
    }

    public void close() {
        synchronized (lock) {
            this.networkInfoProvider.unregisterNetworkChangeListener(this.networkChangeListener);
            try {
                CommonEventManager.unsubscribeCommonEvent(this.priorityBackoffResetReceiver);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    private void increaseBackOffTime() {
        backOffTime = backOffTime == Defaults.DEFAULT_PRIORITY_QUEUE_INTERVAL_IN_MILLISECONDS ? 6000L : backOffTime * 2L;
        long minutes = TimeUnit.MILLISECONDS.toMinutes(this.backOffTime);
        this.logger.d("PriorityIterator backoffTime increased to " + minutes + " minute(s)");
    }

    public int getDownloadConcurrentLimit() {
        return this.downloadConcurrentLimit;
    }

    public void setDownloadConcurrentLimit(int var1) {
        this.downloadConcurrentLimit = var1;
    }

    /**
     * PriorityListProcessorImpl
     *
     * @param handlerWrapper handlerWrapper
     * @param downloadProvider downloadProvider
     * @param downloadManager downloadManager
     * @param networkInfoProvider networkInfoProvider
     * @param logger logger
     * @param listenerCoordinator listenerCoordinator
     * @param downloadConcurrentLimit downloadConcurrentLimit
     * @param context context
     * @param namespace namespace
     * @param prioritySort prioritySort
     */
    public PriorityListProcessorImpl(@NotNull HandlerWrapper handlerWrapper, @NotNull DownloadProvider downloadProvider, @NotNull DownloadManager downloadManager,
                                     @NotNull NetworkInfoProvider networkInfoProvider, @NotNull Logger logger, @NotNull ListenerCoordinator listenerCoordinator,
                                     int downloadConcurrentLimit, @NotNull Context context, @NotNull String namespace, @NotNull PrioritySort prioritySort) {
        super();
        this.handlerWrapper = handlerWrapper;
        this.downloadProvider = downloadProvider;
        this.downloadManager = downloadManager;
        this.networkInfoProvider = networkInfoProvider;
        this.logger = logger;
        this.listenerCoordinator = listenerCoordinator;
        this.downloadConcurrentLimit = downloadConcurrentLimit;
        this.context = context;
        this.namespace = namespace;
        this.prioritySort = prioritySort;
        this.lock = new Object();
        this.globalNetworkType = NetworkType.GLOBAL_OFF;
        this.stopped = true;
        this.backOffTime = 500L;
        this.networkChangeListener = new NetworkInfoProvider.NetworkChangeListener() {

            @Override
            public void onNetworkChanged() {
                PriorityListProcessorImpl.this.handlerWrapper.post(new Runnable() {
                    @Override
                    public void run() {
                        if (!stopped && !paused && networkInfoProvider.isNetworkAvailable() &&
                            backOffTime > Defaults.DEFAULT_PRIORITY_QUEUE_INTERVAL_IN_MILLISECONDS) {
                            resetBackOffTime();
                        }
                    }
                });
            }
        };
        this.networkInfoProvider.registerNetworkChangeListener(this.networkChangeListener);
        registerCommonEvent();
    }

    Runnable priorityIteratorRunnable = new Runnable() {
        @Override
        public void run() {
            if (PriorityListProcessorImpl.this.canContinueToProcess()) {
                if (PriorityListProcessorImpl.this.downloadManager.canAccommodateNewDownload() && PriorityListProcessorImpl.this.canContinueToProcess()) {
                    List priorityList = PriorityListProcessorImpl.this.getPriorityList();
                    boolean shouldBackOff = false;
                    if (priorityList.isEmpty() || !PriorityListProcessorImpl.this.networkInfoProvider.isNetworkAvailable()) {
                        shouldBackOff = true;
                    }
                    if (!shouldBackOff) {
                        shouldBackOff = true;
                        for (int index = 0; index < priorityList.size(); index++) {
                            if (downloadManager.canAccommodateNewDownload() && canContinueToProcess()) {
                                Download download = (Download) priorityList.get(index);
                                boolean isFetchServerRequest = FetchCoreUtils.isFetchFileServerUrl(download.getUrl());
                                if ((isFetchServerRequest || networkInfoProvider.isNetworkAvailable()) && canContinueToProcess()) {
                                    NetworkType networkType = null;
                                    if (globalNetworkType != NetworkType.GLOBAL_OFF) {
                                        networkType = globalNetworkType;
                                    } else if (download.getNetworkType() == NetworkType.GLOBAL_OFF) {
                                        networkType = NetworkType.ALL;
                                    } else {
                                        networkType = download.getNetworkType();
                                    }
                                    boolean properNetworkConditions = networkInfoProvider.isOnAllowedNetwork(networkType);
                                    if (!properNetworkConditions) {
                                        System.out.println("properNetworkConditions = false ");
                                        listenerCoordinator.getMainListener().onWaitingNetwork(download);
                                    }
                                    if ((isFetchServerRequest || properNetworkConditions)) {
                                        shouldBackOff = false;
                                        if (!downloadManager.contains(download.getId()) && canContinueToProcess()) {
                                            downloadManager.start(download);
                                        }
                                    }
                                } else {
                                    break;
                                }
                            } else {
                                break;
                            }
                        }
                    }
                    if (shouldBackOff) {
                        increaseBackOffTime();
                    }
                }
                if (PriorityListProcessorImpl.this.canContinueToProcess()) {
                    PriorityListProcessorImpl.this.registerPriorityIterator();
                }
            }
        }
    };

    private void registerCommonEvent() {
        MatchingSkills matchingSkills = new MatchingSkills();
        matchingSkills.addEvent(ACTION_QUEUE_BACKOFF_RESET);
        CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);
        priorityBackoffResetReceiver = new PriorityBackoffResetReceiver(subscribeInfo);
        try {
            CommonEventManager.subscribeCommonEvent(priorityBackoffResetReceiver);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    class PriorityBackoffResetReceiver extends CommonEventSubscriber {

        public PriorityBackoffResetReceiver(CommonEventSubscribeInfo subscribeInfo) {
            super(subscribeInfo);
        }

        @Override
        public void onReceiveEvent(CommonEventData commonEventData) {
            if (context != null && commonEventData.getIntent() != null) {
                String action = commonEventData.getIntent().getAction();
                if (action != null && action.equals(ACTION_QUEUE_BACKOFF_RESET)) {
                    if (!stopped && !paused && namespace == commonEventData.getIntent().getStringParam(FetchIntent.EXTRA_NAMESPACE)) {
                        resetBackOffTime();
                    }
                }
            }
        }
    }

    public static void setStopped(PriorityListProcessorImpl pl, boolean stopped) {
        pl.stopped = stopped;
    }

    public static void setPaused(PriorityListProcessorImpl pl, boolean paused) {
        pl.paused = paused;
    }

    public static void setBackOffTime(PriorityListProcessorImpl pl, long backOffTime) {
        pl.backOffTime = backOffTime;
    }

}
